package com.xyrl.third.aliyun.service;

import cn.hutool.core.io.FileTypeUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReUtil;
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.*;
import com.xyrl.third.aliyun.enu.EContentType;
import com.xyrl.third.aliyun.util.OssPage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;

/**
 * 阿里云 oss 上传公用工具类
 *
 * @author zhongp
 * @version 1.0
 * @className AliOssService
 * @company 星纭融链科技服务有限公司
 * @date 2019-12-27 14:58:16
 * @Description
 */
@Slf4j
public class OssService {

    /***
     * 自定义域名
     */
    private String customDomain;

    /***
     * 存储空间的名称（桶名称）
     */
    private String bucketName;

    /***
     * 图片保存路径
     */
    private String picLocation;

    /***
     * 最大上传文件的大小
     */
    private Long maxUploadFileSize;

    /***
     *访问资源最大有效时间
     */
    private Long defaultRequsetTimeOut;

    private OSS oss;

    /***
     * 设置存储空间名称（桶名称）
     */
    public void setBucketName(String bucketName) {
        this.bucketName = bucketName;
    }

    /***
     * 获取最大上传文件的限制
     */
    public Long getMaxUploadFileSize() {
        return maxUploadFileSize;
    }

    /***
     * 设置最大上传文件的限制
     */
    public void setMaxUploadFileSize(Long maxUploadFileSize) {
        this.maxUploadFileSize = maxUploadFileSize;
    }

    /***
     * 设置 访问资源最大有效时间
     */
    public void setDefaultRequsetTimeOut(Long defaultRequsetTimeOut) {
        this.defaultRequsetTimeOut = defaultRequsetTimeOut;
    }

    public String getPicLocation() {
        return this.picLocation;
    }

    /***
     * 设置图片保存路径
     */
    public void setPicLocation(String picLocation) {
        this.picLocation = picLocation;
    }

    public String getCustomDomain() {
        return this.customDomain;
    }

    /***
     * 设置自定义域名
     */
    public void setCustomDomain(String customDomain) {
        this.customDomain = customDomain;
    }

    public void setOssClient(OSS oss) {
        this.oss = oss;
    }

    /****
     *
     * @Title: uploadFile
     * @Description: 上传object 理论上来讲 object可以是文本、照片等文件； key为文件名
     * @param bucketName  存储空间名称()
     * @param uploadFileDir  存放在存储空间上的路径(可用空)
     * @param filePath    本地需要上传文件的路径
     * @return: void
     * @author: 黄胜明
     * @date: 2018年5月17日 下午4:27:38
     * @version: V1.0
     * @throws IOException
     */
    public OSSObject uploadFile(String bucketName, String uploadFileDir, String filePath) {
        try {
            File file = new File(filePath);
            InputStream instream = new FileInputStream(file);
            return uploadFileByInputStream(instream, bucketName, uploadFileDir);
        } catch (OSSException e) {
            log.error("uploadFile error", e);
        } catch (FileNotFoundException e) {
            log.error("uploadFile error", e);
        } catch (ClientException e) {
            log.error("uploadFile error", e);
        }
        return null;
    }

    public OSSObject uploadFile(String uploadFileDir, String filePath) {
        return uploadFile(bucketName, uploadFileDir, filePath);
    }

    /**
     * @param bucketName bucketName名称
     * @return
     * @Title: listObject
     * @Description: 遍历查看某个Bucket的所有的object并输出(object相当于目录或图片)
     * @return: ObjectListing
     * @author: 黄胜明
     * @date: 2018年5月17日 下午5:38:00
     * @version: V1.0
     */
    public ObjectListing listObject(String bucketName) {
        // 获取指定bucket 下的所有object信息
        ObjectListing listing = oss.listObjects(bucketName);
//		// 遍历所有object(object相当于目录或图片)
//		for (OSSObjectSummary objectSummary : listing.getObjectSummaries()) {
//			System.out.println(objectSummary.getKey());
//		}
        return listing;
    }

    public ObjectListing listObject() {
        return listObject(bucketName);
    }

    /***
     *
     * @Title: getObject
     * @Description: 获取指定的object
     * @param bucketName
     * @param objectName
     * @return
     * @throws IOException
     * @return: OSSObject
     * @author: 黄胜明
     * @date: 2018年5月17日 下午5:40:16
     * @version: V1.0
     */
    public OSSObject getObject(String bucketName, String objectName) throws IOException {
        OSSObject object = oss.getObject(bucketName, objectName);
        InputStream objectContent = object.getObjectContent();
        objectContent.close();
        return object;
    }

    public OSSObject getObject(String objectName) throws IOException {
        return getObject(bucketName, objectName);
    }

    /**
     * getObjectContent
     *
     * @param bucketName
     * @param objectName
     * @return
     * @throws IOException
     * @Description: 获取指定的object的内容流
     */
    public InputStream getObjectContent(String bucketName, String objectName) {
        OSSObject object = oss.getObject(bucketName, objectName);
        return object.getObjectContent();
    }

    public InputStream getObjectContent(String objectName) {
        return getObjectContent(bucketName, objectName);
    }

    /***
     *
     * @Title: getObject
     * @Description: 获取指定的object文件元信息(只能获取文件的ETag 、 Size （ 文件大小 ） 、 LastModified （ 最后修改时间)
     * @param bucketName
     * @param objectName
     * @return
     * @throws IOException
     * @return: OSSObject
     * @author: 黄胜明
     * @date: 2018年5月17日 下午5:40:16
     * @version: V1.0
     */
    public SimplifiedObjectMeta getSimplifiedObjectMeta(String bucketName, String objectName) throws IOException {
        // 获取文件的部分元信息
        SimplifiedObjectMeta objectMeta = oss.getSimplifiedObjectMeta(bucketName, objectName);
        return objectMeta;
    }

    public SimplifiedObjectMeta getSimplifiedObjectMeta(String objectName) throws IOException {
        return getSimplifiedObjectMeta(bucketName, objectName);
    }

    /***
     *
     * @Title: getObject
     * @Description: 能获取Object文件的全部元数据
     * @param bucketName
     * @param objectName
     * @return
     * @throws IOException
     * @return: OSSObject
     * @author: 黄胜明
     * @date: 2018年5月17日 下午5:40:16
     * @version: V1.0
     */
    public ObjectMetadata getObjectMetadata(String bucketName, String objectName) throws IOException {
        // 获取文件的部分元信息
        ObjectMetadata objectMeta = oss.getObjectMetadata(bucketName, objectName);
        return objectMeta;
    }

    public ObjectMetadata getObjectMetadata(String objectName) throws IOException {
        return getObjectMetadata(bucketName, objectName);
    }

    /***
     *
     * @Title: getPage
     * @Description: 分页查询
     * @param maxKeys 最大条数（不定义则默认为100条）
     * @param currentPage 当前页码
     * @param prefix 目录前缀
     * @param nextMarker 下一个查询标记（第一次查询时为空）
     * @param bucketName 存储空间名称
     * @return
     * @return: OssPage
     * @author: 黄胜明
     * @date: 2018年5月18日 下午5:36:32
     * @version: V1.0
     */
    public OssPage getPage(Integer maxKeys, Integer currentPage, String prefix, String nextMarker, String bucketName) {
        OssPage page = new OssPage(maxKeys, currentPage);
        if (currentPage <= 1) {
            // 查询第一页时，查询标记默认为空
            nextMarker = prefix;
        }
        ListObjectsRequest listObjectsRequest = new ListObjectsRequest(bucketName);
        listObjectsRequest.setPrefix(prefix);
        listObjectsRequest.setMaxKeys(maxKeys);
        listObjectsRequest.setMarker(nextMarker);
        // 指定Object名称编码传输
        // listObjectsRequest.setEncodingType("url");
        ObjectListing objectListing = oss.listObjects(listObjectsRequest);
        page.setBucketName(bucketName);
        page.setMaxKeys(maxKeys);
        if (objectListing != null) {
            page.setMarker(prefix);
            page.setCommonPrefixes(objectListing.getCommonPrefixes());
            page.setObjectSummaries(objectListing.getObjectSummaries());
            nextMarker = objectListing.getNextMarker();
            log.info("getPage nextMarker:::{}", nextMarker);
            page.setNextMarker(nextMarker);
            page.setRequestId(objectListing.getRequestId());
            page.setTruncated(objectListing.isTruncated());
            page.setCurrentPage(currentPage);
            List<String> prefixes = new ArrayList<String>();
            prefixes.add(prefix);
            page.setCommonPrefixes(prefixes);
            boolean isTruncated = objectListing.isTruncated();
            int totalRows = 0;
            int totalPage = 1;
            if (currentPage == 1 && !isTruncated) {
                // 第一次查询已经把所有数据查询完成，直接展示总条数
                totalRows = objectListing.getObjectSummaries().size();
            } else {
                // 第一次查询没有把数据全部查询完，还有更多的数据，需要计算总条数
                totalRows = getTotalRows(prefix, bucketName);
                totalPage = (totalRows % maxKeys == 0) ? (totalRows / maxKeys) : (totalRows / maxKeys) + 1;
            }
            page.setTotalRows(totalRows);
            page.setTotalPage(totalPage);
        }
        return page;
    }

    public OssPage getPage(Integer maxKeys, Integer currentPage, String prefix, String nextMarker) {
        return getPage(maxKeys, currentPage, prefix, nextMarker, bucketName);
    }

    /***
     *
     * @Title: getTotalRow
     * @Description: 查询总条数
     * @param prefix 目录前缀 如：a/b/c
     * @param bucketName 存储空间名称
     * @return
     * @return: Integer
     * @author: 黄胜明
     * @date: 2018年5月19日 上午10:29:30
     * @version: V1.0
     */
    public Integer getTotalRows(String prefix, String bucketName) {
        Integer totalRow = 0;
        ObjectListing objectListing = null;
        boolean isTruncated = false;
        String nextMarket = prefix;
        do {
            ListObjectsRequest listObjectsRequest = new ListObjectsRequest(bucketName);
            listObjectsRequest.setPrefix(prefix);
            listObjectsRequest.setMaxKeys(3); // 默认为系统默认值 1000
            listObjectsRequest.setMarker(nextMarket);
            // 指定Object名称编码传输
            // listObjectsRequest.setEncodingType("url");
            objectListing = oss.listObjects(listObjectsRequest);
            nextMarket = objectListing.getNextMarker();
            isTruncated = objectListing.isTruncated();
            totalRow += objectListing.getObjectSummaries().size();
            // // 遍历所有Object
            // System.out.println("Objects:");
            // for (OSSObjectSummary objectSummary : objectListing.getObjectSummaries()) {
            // System.out.println(objectSummary.getKey());
            // }
            // // 遍历所有CommonPrefix
            // System.out.println("\nCommonPrefixs:");
            // for (String commonPrefix : objectListing.getCommonPrefixes()) {
            // System.out.println(commonPrefix);
            // }
        } while (isTruncated);
        return totalRow;
    }

    public Integer getTotalRows(String prefix) {
        return getTotalRows(prefix, bucketName);
    }

    /***
     *
     * @Title: getObjectAcl
     * @Description: 获取指定的object的权限
     * @param bucketName
     * @param objectName
     * @return
     * @throws IOException
     * @return:
     * @author: 黄胜明
     * @date: 2018年5月18日 下午3:33:18
     * @version: V1.0
     */
    public ObjectAcl getObjectAcl(String bucketName, String objectName) throws IOException {
        // 读取Object ACL
        ObjectAcl objectAcl = oss.getObjectAcl(bucketName, objectName);
        return objectAcl;
    }

    public ObjectAcl getObjectAcl(String objectName) throws IOException {
        return getObjectAcl(bucketName, objectName);
    }

    /****
     *
     * @Title: downloadFile
     * @Description: 下载图片
     *    下载时路径要写到具体文件名，如：下载objectName为"字幕.txt"，那么下载到桌面的路径为".../Desktop/字幕.txt"
     * @param bucketName bucket名称
     * @param objectName 图片名称或目录名称
     * @param filePath  下载的图片或目录需要保存的绝对路径
     * @return: void
     * @author: 黄胜明
     * @date: 2018年5月17日 下午5:42:01
     * @version: V1.0
     */
    public void downloadFile(String bucketName, String objectName, String filePath) {
        try {
            File file = new File(filePath);
            GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, objectName);
            oss.getObject(getObjectRequest, file);
            log.info("下载文件:[{}]完成,客户端保存路径:{}", objectName, filePath);
        } catch (OSSException e) {
            log.info("下载文件:[{}]失败,客户端保存路径:{}", objectName, filePath, e);
        } catch (ClientException e) {
            log.info("下载文件:[{}]失败,客户端保存路径:{}", objectName, filePath, e);
        }
    }

    public void downloadFile(String objectName, String filePath) {
        downloadFile(bucketName, objectName, filePath);
    }

    /***
     *
     * @Title: restoreObject
     * @Description: 归档文件类型文件在访问或下载前需要进行解冻，否则无法时行 访问
     * @param bucketName 存储空间名称
     * @param objectName 存储空间下文件的路径
     * @return
     * @return: boolean
     * @author: 黄胜明
     * @date: 2018年5月18日 下午2:18:57
     * @version: V1.0
     */
    public boolean restoreObject(String bucketName, String objectName) {
        try {
            ObjectMetadata objectMetadata = oss.getObjectMetadata(bucketName, objectName);
            StorageClass storageClass = objectMetadata.getObjectStorageClass();
            if (storageClass == StorageClass.Archive) {
                oss.restoreObject(bucketName, objectName);
                do {
                    Thread.sleep(1000);
                    objectMetadata = oss.getObjectMetadata(bucketName, objectName);
                } while (!objectMetadata.isRestoreCompleted());
            }
            OSSObject ossObject = oss.getObject(bucketName, objectName);
            ossObject.getObjectContent().close();
            return true;
        } catch (OSSException e) {
            log.error("uploadFile error", e);
            return false;
        } catch (FileNotFoundException e) {
            log.error("uploadFile error", e);
            return false;
        } catch (ClientException e) {
            log.error("uploadFile error", e);
            return false;
        } catch (IOException e) {
            log.error("uploadFile error", e);
            return false;
        } catch (InterruptedException e) {
            log.error("uploadFile error", e);
            return false;
        }
    }

    public boolean restoreObject(String objectName) {
        return restoreObject(bucketName, objectName);
    }

    /****
     *
     * @Title: deleteObject
     * @Description: 删除某个Bucket的所有的object(object相当于目录或图片)
     * @param bucketName
     * @param objectName
     * @return: void
     * @author: 黄胜明
     * @date: 2018年5月17日 下午5:41:23
     * @version: V1.0
     */
    public void deleteObject(String bucketName, String objectName) {
        oss.deleteObject(bucketName, objectName);
    }

    public void deleteObject(String objectName) {
        deleteObject(bucketName, objectName);
    }

    /***
     *
     * @Title: uploadFileByMultipartFile
     * @Description: 上传流文件，最大上传文件不能超过1M
     * @param url 文件访问地址
     * @param bucketName 存储空间名称（桶名称）
     * @param uploadFileDir 保存的目录
     * @return
     * @throws Exception
     * @return: String
     * @author: 黄胜明
     * @date: 2018年5月21日 下午2:12:43
     * @version: V1.0
     */
    public OSSObject uploadFileByStreamFile(String url, String bucketName, String uploadFileDir) throws Exception {
        try {
            InputStream inputStream = new URL(url).openStream();
            return uploadFileByInputStream(inputStream, bucketName, uploadFileDir);
        } catch (IOException e) {
            throw new Exception("图片请求失败::", e);
        }

    }

    public OSSObject uploadFileByStreamFile(String url, String uploadFileDir) throws Exception {
        return uploadFileByStreamFile(url, bucketName, uploadFileDir);
    }

    /***
     *
     * @Title: uploadFileByMultipartFile
     * @Description: 上传springmvc中的MultipartFile附件，最大上传文件不能超过1M
     * @param file MultipartFile文件
     * @param bucketName 存储空间名称（桶名称）
     * @param uploadFileDir 保存的目录
     * @return
     * @throws Exception
     * @return: String
     * @author: 黄胜明
     * @date: 2018年5月21日 下午2:12:43
     * @version: V1.0
     */
    public OSSObject uploadFileByMultipartFile(MultipartFile file, String bucketName, String uploadFileDir) throws Exception {
        try {
            InputStream inputStream = file.getInputStream();
            return uploadFileByInputStream(inputStream, bucketName, uploadFileDir);
        } catch (Exception e) {
            throw new Exception("图片上传失败");
        }
    }

    public OSSObject uploadFileByMultipartFile(MultipartFile file, String uploadFileDir) throws Exception {
        return uploadFileByMultipartFile(file, bucketName, uploadFileDir);
    }

    /**
     * @param instream      二进制流
     * @param bucketName    存储空间名称（桶名称）
     * @param uploadFileDir 保存的目录
     * @return
     * @Title: uploadFile2OSS
     * @Description: 通过文件流上传文件到oss服务器
     * @return: OSSObject
     * @author: 黄胜明
     * @date: 2018年5月21日 下午2:10:07
     * @version: V1.0
     */
    public OSSObject uploadFileByInputStream(InputStream instream, String bucketName, String uploadFileDir) {
        String filenameExtension = FileTypeUtil.getType(instream);
        String fileName = createFileName(filenameExtension);
        String key = fileName;
        if (ObjectUtil.isNotNull(uploadFileDir)) {
            if (uploadFileDir.endsWith("/")) {
                key = uploadFileDir + getDefaultServerRoot() + key;
            } else {
                key = uploadFileDir + "/" + getDefaultServerRoot() + key;
            }
        } else {
            key = "/" + getDefaultServerRoot();
        }
        key = fileDirHandle(key);
        return uploadInputStream(instream, bucketName, key, fileName);
    }

    public OSSObject uploadFileByInputStream(InputStream instream, String uploadFileDir) {
        return uploadFileByInputStream(instream, bucketName, uploadFileDir);
    }

    /**
     * @param instream   二进制流
     * @param bucketName 存储空间名称（桶名称）
     * @param key        图片Key
     * @param fileName   文件名
     * @return
     * @Title: uploadInputStream
     * @Description: 通过文件流上传文件到oss服务器
     * @return: OSSObject
     * @author: 黄胜明
     * @date: 2018年5月21日 下午2:10:07
     * @version: V1.0
     */
    public OSSObject uploadInputStream(InputStream instream, String bucketName, String key, String fileName) {
        OSSObject ossObject = null;
        try {
            if (instream == null) {
                throw new Exception("上传的资源文件为空");
            }
            byte[] byteArray = inputStreamtoByteArray(instream);
            String filenameExtension = FileTypeUtil.getType(instream);
            instream = byteToInputStream(byteArray);
            int fileSize = instream.available();
            if (fileSize > maxUploadFileSize) {
                throw new Exception("上传的资源文件" + (fileSize / 1048576) + "MB," + "超过" + (maxUploadFileSize / 1048576) + "MB！");
            }
            // 创建上传Object的Metadata
            ObjectMetadata objectMetadata = new ObjectMetadata();
            String contentType = EContentType.getType(filenameExtension);
            objectMetadata.setContentLength(fileSize);
            //objectMetadata.setCacheControl("no-cache");
            //objectMetadata.setHeader("Pragma", "no-cache");
            objectMetadata.setContentType(contentType);
            objectMetadata.setContentDisposition("inline;filename=" + fileName);
            long startTime = System.currentTimeMillis();
            PutObjectResult putResult = oss.putObject(bucketName, key, instream, objectMetadata);
            long endTime = System.currentTimeMillis();
            log.info("上传文件成功: {}, size: {}KB, time: {}ms", key, fileSize / 1024, endTime - startTime);
            if (putResult != null) {
                return getObject(bucketName, key);
            }
        } catch (Exception e) {
            log.error("上传文件失败：", e);
        } finally {
            try {
                if (instream != null) {
                    instream.close();
                }
            } catch (IOException e) {
                log.error("关闭文件流失败：", e);
            }
        }
        return ossObject;
    }

    public OSSObject uploadInputStream(InputStream instream, String key, String fileName) {
        return uploadInputStream(instream, bucketName, key, fileName);
    }

    /***
     *
     * @Title: uploadFileObject
     * @Description: 上传springmvc中的MultipartFile附件，最大上传文件不能超过1M
     * @param file File文件
     * @param bucketName 存储空间名称（桶名称）
     * @param key 图片Key
     * @param fileName   文件名
     * @return
     * @throws Exception
     * @return: String
     * @author: 黄胜明
     * @date: 2018年5月21日 下午2:12:43
     * @version: V1.0
     */
    public OSSObject uploadFileObject(File file, String bucketName, String key, String fileName) throws Exception {
        try {
            InputStream inputStream = new FileInputStream(file);
            try {
                return uploadInputStream(inputStream, bucketName, key, fileName);
            } finally {
                inputStream.close();
            }
        } catch (Exception e) {
            throw new Exception("图片上传失败");
        }
    }

    public OSSObject uploadFileObject(File file, String key, String fileName) throws Exception {
        return uploadFileObject(file, bucketName, key, fileName);
    }

    /**
     * @param bucketName
     * @param key
     * @return
     * @Title: getOauthPresignedUrl
     * @Description: 获取授权链接(适用于 ： 访问的资源是私有的 ， 公开的不需要授权)
     * @return: String
     * @author: 黄胜明
     * @date: 2018年5月21日 下午2:23:06
     * @version: V1.0
     */
    public String getOauthPresignedUrl(String bucketName, String key) {
        // 设置url过期时间
        Date expiration = new Date(System.currentTimeMillis() + defaultRequsetTimeOut);
        // 生成URL
        URL url = oss.generatePresignedUrl(bucketName, key, expiration);
        if (url != null) {
            return url.toString();
        }
        return null;
    }

    public String getOauthPresignedUrl(String key) {
        return getOauthPresignedUrl(bucketName, key);
    }

    /***
     *
     * @Title: fileDir
     * @Description: 处理上传目录（防止目录有"/"开头或各目录之间有多个"/"）
     * @param objectName
     * @return
     * @return: String
     * @author: 黄胜明
     * @date: 2018年5月22日 上午9:31:07
     * @version: V1.0
     */
    private String fileDirHandle(String objectName) {
        if (ObjectUtil.isNotNull(objectName)) {
            objectName = ReUtil.replaceAll(objectName, "\\\\+", "/");
            objectName = ReUtil.replaceAll(objectName, "//+", "/");
            if (objectName.startsWith("/")) {
                objectName = objectName.substring(1);
            }
        }
        return objectName;
    }

    /***
     *
     * @Title: byteToInput
     * @Description: byte字节流数据转InputStream
     * @param buf
     * @return
     * @return: InputStream
     * @author: 黄胜明
     * @date: 2018年5月22日 上午11:35:24
     * @version: V1.0
     */
    private static final InputStream byteToInputStream(byte[] buf) {
        return new ByteArrayInputStream(buf);
    }

    /**
     * @param in
     * @return
     * @throws IOException
     * @Title: inStreamToByte
     * @Description: InputStream 据转byte字节流
     * @return: byte[]
     * @author: 黄胜明
     * @date: 2018年5月22日 上午11:36:04
     * @version: V1.0
     */
    private static byte[] inputStreamtoByteArray(InputStream in) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024 * 4];
        int n = 0;
        while ((n = in.read(buffer)) != -1) {
            out.write(buffer, 0, n);
        }
        return out.toByteArray();
    }

    /***
     *
     * @Title: createFileName
     * @Description: 服务器默认目录
     * @return
     * @return: String
     * @author: 黄胜明
     * @date: 2018年5月22日 下午4:53:56
     * @version: V1.0
     */
    private static String getDefaultServerRoot() {
        return "";
    }

    /***
     *
     * @Title: createFileName
     * @Description: 生成文件名
     * @param filenameExtension
     * @return
     * @return: String
     * @author: 黄胜明
     * @date: 2018年5月22日 下午4:53:56
     * @version: V1.0
     */
    private static String createFileName(String filenameExtension) {
        return UUID.randomUUID().toString().replaceAll("\\-", "") + "." + filenameExtension;
    }
}
